/**
 *
 */

import Util;
import OpenSearchUtil;
import Credential;

type @endpoint = string
type @protocol = string
type @userAgent = string
type @credential = Credential

model Config {
  endpoint?: string,
  protocol?: string,
  type?: string,
  securityToken?: string,
  accessKeyId?: string,
  accessKeySecret?: string,
  userAgent?: string
}

init(config: Config){
  if (Util.isUnset(config)) {
    throw {
      name = 'ParameterMissing',
      message = '\'config\' can not be unset'
    };
  }

  if (Util.empty(config.type)) {
    config.type = 'access_key';
  }
  var credentialConfig = new Credential.Config{
    accessKeyId = config.accessKeyId,
    type = config.type,
    accessKeySecret = config.accessKeySecret,
    securityToken = config.securityToken,
  };
  @credential = new Credential(credentialConfig);
  @endpoint = config.endpoint;
  @protocol = config.protocol;
  @userAgent = config.userAgent;
}

model SearchQuery {
  query: string(name='query', description='搜索主体，不能为空。主要支持子句有 config子句、query子句、sort子句、filter子句、aggregate子句、distinct子句 、kvpairs子句。'),
  fetchFields?: string(name='fetch_fields', description='表示本次查询需要召回哪些字段值，多个字段之间通过英文分号;分隔，对应控制台中的默认展示字段功能。默认使用全部可展示字段。'),
  qp?: string(name='qp', description='指定要使用的查询分析规则，多个规则使用英文逗号,分隔。默认使用已上线规则'),
  disable?: string(name='disable', description='关闭指定已生效的参数功能。'),
  firstRankName?: string(name='first_rank_name', description='设置粗排表达式名字。'),
  secondRankName?: string(name='second_rank_name', description='设置精排表达式名字。'),
  userId?: string(name='user_id', description='用来标识发起当前搜索请求的终端用户。该值可以设置为下列值，优先级从高到低：1. 终端用户的长登录会员ID；2. 终端用户的移动设备imei标识；3. 终端用户的client_ip'),
  abtest?: string(name='abtest', description='使用A/B Test功能时需要设置该参数。'),
  categoryPrediction?: string(name='category_prediction', description='通过类目预测功能搜索时需要设置该参数。'),
  rawQuery?: string(name='raw_query', description='终端用户输入的query，用于类目预测相关模型训练。'),
  summary?: string(name='summary', description='搜索结果摘要配置，可以指定某些字段进行飘红、截断等操作。'),
}

model SearchRequestModel {
  headers?: map[string]string(description='headers', name='headers'),
  query: SearchQuery(description='query', name='query')
}

model SearchResponseModel {
  headers?: map[string]string(description='headers', name='headers'),
  body: SearchResponse(description='body', name='body')
}

/**
 * 系统提供了丰富的搜索语法以满足用户各种场景下的搜索需求。
 */
async function searchEx(appName: string, request: SearchRequestModel, runtime: Util.RuntimeOptions): SearchResponseModel {
  return _request('GET', `/v3/openapi/apps/${appName}/search`, request.query, request.headers, null, runtime);
}

/**
 * 系统提供了丰富的搜索语法以满足用户各种场景下的搜索需求。
 */
async function search(appName: string, request: SearchRequestModel): SearchResponseModel {
  var runtime = new Util.RuntimeOptions{
    connectTimeout = 5000,
    readTimeout = 10000,
    autoretry = false,
    ignoreSSL = false,
    maxIdleConns = 50,
  };
  return searchEx(appName, request, runtime);
}

model SuggestQuery {
  query: string(name='query', description='搜索关键词（包含中文需进行urlencode编码）'),
  hit?: integer(name='hit', description='下拉提示条数', minimum=1, maximum=10, default=10),
}

model SuggestRequestModel {
  headers?: map[string]string(description='headers', name='headers'),
  query: SuggestQuery(description='query', name='query')
}

model SuggestResponseModel {
  headers?: map[string]string(description='headers', name='headers'),
  body: SuggestionResponse(description='body', name='body')
}

/**
 * 下拉提示是搜索服务的基础功能，在用户输入查询词的过程中，智能推荐候选query，减少用户输入，帮助用户尽快找到想要的内容。
 */
async function suggestEx(appName: string, modelName: string, request: SuggestRequestModel, runtime: Util.RuntimeOptions): SuggestResponseModel {
  return _request('GET', `/v3/openapi/apps/${appName}/suggest/${modelName}/search`, request.query, request.headers, null, runtime);
}

/**
 * 下拉提示是搜索服务的基础功能，在用户输入查询词的过程中，智能推荐候选query，减少用户输入，帮助用户尽快找到想要的内容。
 */
async function suggest(appName: string, modelName: string, request: SuggestRequestModel): SuggestResponseModel {
  var runtime = new Util.RuntimeOptions{
    connectTimeout = 5000,
    readTimeout = 10000,
    autoretry = false,
    ignoreSSL = false,
    maxIdleConns = 50,
  };
  return suggestEx(appName, modelName, request, runtime);
}

model PushDocumentRequestModel {
  headers?: map[string]string(description='headers', name='headers'),
  body: [ Document ](description='body', name='body')
}

model PushDocumentResponseModel {
  headers?: map[string]string(description='headers', name='headers'),
  body: Response(description='body', name='body')
}

/**
 * 支持新增、更新、删除 等操作，以及对应批量操作
 */
async function pushDocumentEx(appName: string, tableName: string, request: PushDocumentRequestModel, runtime: Util.RuntimeOptions): PushDocumentResponseModel {
  return _request('POST', `/v3/openapi/apps/${appName}/${tableName}/actions/bulk`, null, request.headers, request.body, runtime);
}

/**
 * 支持新增、更新、删除 等操作，以及对应批量操作
 */
async function pushDocument(appName: string, tableName: string, request: PushDocumentRequestModel): PushDocumentResponseModel {
  var runtime = new Util.RuntimeOptions{
    connectTimeout = 5000,
    readTimeout = 10000,
    autoretry = false,
    ignoreSSL = false,
    maxIdleConns = 50,
  };
  return pushDocumentEx(appName, tableName, request, runtime);
}

model CollectDataRequestModel {
  headers?: map[string]string(description='headers', name='headers'),
  body: [ Behavior ](description='body', name='body')
}

model CollectDataResponseModel {
  headers?: map[string]string(description='headers', name='headers'),
  body: Response(description='body', name='body')
}

/**
 * 为了给客户提供更高质量的搜索效果，opensearch目前支持客户通过server端上传点击数据。
 */
async function collectDataEx(appName: string, collectorName: string, request: CollectDataRequestModel, runtime: Util.RuntimeOptions): CollectDataResponseModel {
  return _request('POST', `/v3/openapi/app-groups/${appName}/data-collections/${collectorName}/actions/bulk`, null, request.headers, request.body, runtime);
}

/**
 * 为了给客户提供更高质量的搜索效果，opensearch目前支持客户通过server端上传点击数据。
 */
async function collectData(appName: string, collectorName: string, request: CollectDataRequestModel): CollectDataResponseModel {
  var runtime = new Util.RuntimeOptions{
    connectTimeout = 5000,
    readTimeout = 10000,
    autoretry = false,
    ignoreSSL = false,
    maxIdleConns = 50,
  };
  return collectDataEx(appName, collectorName, request, runtime);
}

/**
 * 
 */
model SearchResponse {
  status?: string(name='status'),
  requestId?: string(name='request_id'),
  result?: SearchResult(name='result'),
  errors?: [ Error ](name='errors'),
}

/**
 * 实际返回结果，包括查询耗时searchtime、引擎总结果数total、本次请求返回结果数num、本次查询最大返回结果数viewtotal、查询结果items、统计结果facet等信息
 */
model SearchResult {
  searchtime?: double(name='searchtime', description='指引擎耗时，单位为秒。'),
  total?: integer(name='total', description='total为一次查询（不考虑config子句）引擎中符合条件的结果数（在结果数较多情况下，该值会做优化），一般用来做展示。'),
  viewtotal?: integer(name='viewtotal', description='考虑到性能及相关性，引擎最多会返回viewtotal个结果。如果需要翻页的话，要求start+hit必需要小于viewtotal'),
  num?: integer(name='num', description='num为本次查询请求（受限于config子句的start及hit）实际返回的条目，不会超过hit值。'),
  items?: [ object ](name='items', description='包含查询召回数据信息，其中fields为搜索召回内容。'),
  facet?: [ SearchResultFacet ](name='facet', description='用于存放Aggregate子句返回的信息。'),
}

/**
 * 
 */
model SearchResultItemFullJson {
  fields?: map[string]any(name='fields', description='搜索召回内容'),
  variableValue?: map[string]any(name='variableValue', description='表示自定义参数返回结果，如获取distance距离值，variableValue 节点只有在config子句的format为xml或者fulljson时才能展现出来，json格式默认不展示。'),
  sortExprValues?: [ number ](name='sortExprValues', description='表示对应文档排序分。'),
}

/**
 * 
 */
model SearchResultFacet {
  key?: string(name='key'),
  items?: [
    {
      value?: string(name='value'),
      count?: integer(name='count'),
    }
  ](name='items'),
}

/**
 * 
 */
model Error {
  code?: integer(name='code', description='错误代码'),
  message?: string(name='message', description='错误描述'),
}

/**
 * 
 */
model Document {
  cmd: string(name='cmd', description='必选字段。定义该文档的操作行为，可以为“add”、“update”、“delete”，标准版不支持“update”。建议一个请求中进行批量更新操作，提高网络交互及处理效率。“add”表示新增文档，如果该主键对应文档已经存在，则执行先“delete”再“add”的操作；“update”表示更新文档，对该主键对应文档进行部分字段更新；“delete”表示删除文档，如果该主键对应文档已经不存在，则认为删除成功。', enum='add, update, delete'),
  timestamp?: integer(name='timestamp', description='可选字段。用来记录文档实际发生时间，单位为毫秒。系统会用该时间戳来作为同一主键文档更新顺序的判断标准，该选项仅支持高级版，标准版应用，没有该timestamp选项，若指定该选项，推送会报4007错误码。在没有该timestamp项时，默认以文档发送到OpenSearch的时间作为文档更新时间进行操作。'),
  fields: map[string]any(name='fields', description='必选字段。要操作的文档内容，主键字段必选，系统所有操作都是通过主键来进行的。对于“delete”只需要提供文档主键即可。'),
}

/**
 * 
 */
model Behavior {
  cmd: string(name='cmd', description='必选字段。定义该文档的操作行为，目前仅支持ADD', enum='ADD'),
  fields: map[string]any(name='fields', description='必选字段。行为数据主体。'),
}

/**
 * object_id=pk,object_type=ops_search_doc,ops_request_misc=xxx
 */
model Event2001Args {
  objectId?: string(name='object_id', description='被点击的对象的主键，不能为空，如果通过api上传需要urlencode'),
  objectType?: string(name='object_type', description='必须为ops_search_doc', default='ops_search_doc'),
  opsRequestMisc?: string(name='ops_request_misc', description='开放搜索在搜索结果中返回的参数，只要原样设置即可。'),
}

/**
 * 
 */
model SuggestionResponse {
  requestId?: string(name='request_id'),
  searchtime?: double(name='searchtime', description='引擎查询耗时，单位为秒'),
  suggestions?: [
    {
      suggestion?: string(name='suggestion'),
    }
  ](name='suggestions'),
  errors?: [ Error ](name='errors'),
}

/**
 * 
 */
model Response {
  status?: string(name='status'),
  requestId?: string(name='request_id'),
  errors?: [ Error ](name='errors'),
}

api _request(method: string, pathname: string, query: map[string]any, headers: map[string]string, body: any, runtime: Util.RuntimeOptions): map[string]any {
  var accesskeyId = getAccessKeyId();
  var accessKeySecret = getAccessKeySecret();
  __request.protocol = Util.defaultString(@protocol, 'HTTP');
  __request.method = method;
  __request.pathname = pathname;
  __request.headers = {
    user-agent = getUserAgent(),
    Date = OpenSearchUtil.getDate(),
    host = Util.defaultString(@endpoint, `opensearch-cn-hangzhou.aliyuncs.com`),
    X-Opensearch-Nonce = Util.getNonce(),
    ...headers
  };

  if (!Util.isUnset(query)) {
    __request.query = Util.stringifyMapValue(query);
  }

  if (!Util.isUnset(body)) {
    var reqBody = Util.toJSONString(body);
    __request.headers.Content-MD5 = OpenSearchUtil.getContentMD5(reqBody);
    __request.headers.Content-Type = 'application/json';
    __request.body = reqBody;
  }
  __request.headers.Authorization = OpenSearchUtil.getSignature(__request, accesskeyId, accessKeySecret);
} returns {
  var objStr = Util.readAsString(__response.body);

  if (Util.is4xx(__response.statusCode) || Util.is5xx(__response.statusCode)) {
    throw {
      message = __response.statusMessage,
      data = objStr,
      code = __response.statusCode
    };
  }
  var obj = Util.parseJSON(objStr);
  var res = Util.assertAsMap(obj);
  return {
    body = res,
    headers = __response.headers
  };
} runtime {
  timeouted = 'retry',
  readTimeout = runtime.readTimeout,
  connectTimeout = runtime.connectTimeout,
  httpProxy = runtime.httpProxy,
  httpsProxy = runtime.httpsProxy,
  noProxy = runtime.noProxy,
  maxIdleConns = runtime.maxIdleConns,
  retry = {
    retryable = runtime.autoretry,
    maxAttempts = Util.defaultNumber(runtime.maxAttempts, 3)
  },
  backoff = {
    policy = Util.defaultString(runtime.backoffPolicy, 'no'),
    period = Util.defaultNumber(runtime.backoffPeriod, 1)
  },
  ignoreSSL = runtime.ignoreSSL
}

function setUserAgent(userAgent: string): void {
  @userAgent = userAgent;
}

function appendUserAgent(userAgent: string): void {
  @userAgent = `${@userAgent} ${userAgent}`;
}

function getUserAgent(): string {
  var userAgent = Util.getUserAgent(@userAgent);
  return userAgent;
}

async function getAccessKeyId(): string {
  if (Util.isUnset(@credential)) {
    return '';
  }
  var accessKeyId = @credential.getAccessKeyId();
  return accessKeyId;
}

async function getAccessKeySecret(): string {
  if (Util.isUnset(@credential)) {
    return '';
  }
  var secret = @credential.getAccessKeySecret();
  return secret;
}
